/**
 * 
 */
package mdp.id;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import mdp.id.RDDL2DD.DEBUG;

import dd.discrete.ADD;
import dd.discrete.ADDDNode;
import dd.discrete.ADDINode;
import dd.discrete.ADDNode;
import dd.discrete.DD;

import rddl.RDDL;
import rddl.RDDL.DOMAIN;
import rddl.RDDL.INSTANCE;
import rddl.RDDL.LCONST;
import rddl.RDDL.NONFLUENTS;
import rddl.State;
import rddl.parser.parser;
import rddl.RDDL.PVAR_NAME;
import util.Pair;

/**
 * @author NR
 *
 * TODO
 */
public class ADDHandler extends RDDL2DD<ADD> {

	private Map<Pair<Integer, Double>, Integer>	makeNegInfCache = new HashMap<Pair<Integer,Double>, Integer>();
	private int	policyHit = 0;
	private Map<Pair<Integer, Map<String, Boolean>>, Integer> policyCache = new HashMap<Pair<Integer, Map<String, Boolean>>, Integer>();
	
	/**
	 * @param pSolutionInfo
	 */
	public ADDHandler(DEBUG dbg, ORDER ord) {
		_debug = dbg;
		_order = ord;
	}

	/* (non-Javadoc)
	 * @see mdp.id.RDDL2DD#createInstance()
	 */
	@Override
	protected ADD createInstance() {
		return new ADD(_alOrder);
	}

	/* (non-Javadoc)
	 * @see mdp.id.RDDL2DD#getMax(int)
	 */
	@Override
	public double getMax(int dd) {
		return _context.getMaxValue(dd);
	}


	/* (non-Javadoc)
	 * @see mdp.id.RDDL2DD#getMin(int)
	 */
	@Override
	public double getMin(int dd) {
		return _context.getMinValue(dd);
	}

	/* (non-Javadoc)
	 * @see mdp.id.RDDL2DD#getNegInf()
	 */
	@Override
	public double getNegInf() {
		return ADD.NEGINF;
	}

	@Override
	public void showGraph(int dd) {
		_context.getGraph(dd, _tmID2VarName).launchViewer();
	}

	@Override
	public int ConstrainThis(int dd, Map<Integer, Boolean> act,
			boolean stateact, boolean flush,
			final int... save) {
		if( _debug.compareTo(DEBUG.SOLUTION_INFO) >= 0 ) {
			System.out.println("Constraining " + getSize(dd));
			System.out.println(showMemory());
		}
		int ret = dd;
		for( Pair<Integer,Integer> con : _hsPairConstraints ) {
			int adder = con._o2;
			adder = isExogenous(adder) ? adder : simplify(adder, act);
			
			if( ( !stateact && isExogenous(adder) ) || ( stateact ) ) {
				ret = _context.applyInt(ret, adder, DD.ARITH_SUM);	
			}
			
			if( flush ) {
				saveThis(ret);
				flushCaches(false, save);
				forgetThis(ret);
			}
			
			
		}
		return ret;
	}
	
	
	private int argMaxHelper(int dd) {
		ADDNode node = _context.getNode(dd);
		if( node instanceof ADDDNode ){
			return DD_ZERO;
		}
		
		ADDINode inode = (ADDINode)node;
		
		int varid = inode._nTestVarID;
		String var = getActualVarName(varid);
		
		if( _tmStateVars.get(var) != null ) {
			int truebr = _context.getVarNode(varid, 0.0d, 1.0d);
			int truedd = argMaxHelper(inode._nHigh);
			if( truedd == DD_NEG_INF ) {
				truebr = _context.getVarNode(varid, 0.0d, getNegInf());
			}else {
				truebr = _context.applyInt(truebr, truedd, DD.ARITH_PROD);
			}
			
			int falsebr = _context.getVarNode(varid, 1.0d, 0.0d);
			int falsedd =  argMaxHelper(inode._nLow);
			if( falsedd == DD_NEG_INF ) {
				falsebr = _context.getVarNode(varid, getNegInf(), 0.0d);
			}else {
				falsebr = _context.applyInt(falsebr, falsedd, DD.ARITH_PROD);
			}
			
			int ret = _context.applyInt(falsebr, truebr , DD.ARITH_SUM);
			return ret;
		}else if( _tmActionVars.get(var) != null ) {
			
			int ret = DD_ZERO;
			
			while( true ){
				
				
					if( node instanceof ADDINode ) {
						ADDINode nod = (ADDINode)node;
						ADDNode truthnode = _context.getNode(nod._nHigh);
						ADDNode falsenode = _context.getNode(nod._nLow);						
						double max = nod._dMaxUpper;
						double maxt, maxf;
					
						if( truthnode instanceof ADDDNode ) {
							maxt = ((ADDDNode)truthnode)._dUpper;
						}else {
							maxt = ((ADDINode)truthnode)._dMaxLower;
						}
					
						if( falsenode instanceof ADDDNode ) {
							maxf = ((ADDDNode)falsenode)._dUpper;
						}else {
							maxf = ((ADDINode)falsenode)._dMaxLower;
						}
					
						int thiscon;
						if( max == maxf ) {
							node = falsenode;
							thiscon = _context.getVarNode(nod._nTestVarID, 0.0d, getNegInf());
						}else {
							node = truthnode;
							thiscon = _context.getVarNode(nod._nTestVarID, getNegInf(), 0.0d);
						}
						ret = _context.applyInt(ret, thiscon, DD.ARITH_SUM);
					}else {
						break;
					}
				}
			return ret;
		}else{
		System.err.println("next state!!!");
		System.exit(1);
		}
			
		return -1;
		
	}

	/* (non-Javadoc)
	 * @see mdp.id.RDDL2DD#argMaxToNegInf(int, int)
	 */
	@Override
	public int argMaxToNegInf(int pV, int pQ) {
		pV = fix(pV);
		pQ = fix(pQ);
		
		int diff = _context.applyInt(pV, pQ, DD.ARITH_MINUS);
		int actzero = thresholdAt(diff, 0.0d);
		
		int ret = makeNegInfDD(actzero, 1.0d);
		return ret;
	}
	
	@Override
	public int makePolicy(int v, int q){
		
		v = fix(v);
		q = fix(q);
		
		int diff = _context.applyInt(v, q, DD.ARITH_MINUS);
		int actzerro = thresholdAt(diff, 0.0d);
		int actone = _context.applyInt(DD_ONE, actzerro, DD.ARITH_MINUS);
		
		int ret = makePolicyHelper(actone, new HashMap<String,Boolean>());
		
		System.out.println(policyHit);
		policyHit = 0;
		policyCache.clear();
		
		return ret;
	}
	
	@Override
	public int makePolicyHelper(int dd, Map<String, Boolean> assign) {
		
		if( policyCache.get(new Pair<Integer, Map<String, Boolean>>(dd, assign)) != null ) {
			++policyHit ;
			return policyCache.get(new Pair<Integer, Map<String, Boolean>>(dd, assign));
		}
		
		ADDNode node = _context.getNode(dd);
		if( node instanceof ADDDNode ){
			if( ((ADDDNode)node)._dLower == 1 ) {
				Map<Integer, Boolean> ints = convertToInts(assign);
				long actid = getJointActionID(ints);
				return _context.getConstantNode(actid);
			}else {
				return DD_ZERO;
			}
			
		}
		
		ADDINode inode = (ADDINode)node;
		
		int varid = inode._nTestVarID;
		String var = getActualVarName(varid);
		int ret;
		
		Map<String, Boolean> thisAssign = new HashMap<String, Boolean>(assign);
		if( _alactionvars.contains(var) ) {
			thisAssign.put(var, true);
			int truedd = makePolicyHelper(inode._nHigh, thisAssign);
			
			thisAssign.put(var, false);
			int falsedd =  makePolicyHelper(inode._nLow, thisAssign);
			
			//places where truedd has an action
			int truepol = thresholdAt(truedd, 0.0d);
			int zeroout = _context.applyInt(DD_ONE, truepol, DD.ARITH_MINUS);
			
			ret = _context.applyInt(
					truedd, 
					_context.applyInt(falsedd, zeroout, DD.ARITH_PROD), 
				DD.ARITH_SUM);
		}else {
			int truedd = makePolicyHelper(inode._nHigh, thisAssign);
			int falsedd =  makePolicyHelper(inode._nLow, thisAssign);
			int truebr = _context.getVarNode(varid, 0.0d, 1.0d);
			int falsebr = _context.getVarNode(varid, 1.0d, 0.0d);
			
			ret = _context.applyInt(
					_context.applyInt(truebr, truedd, DD.ARITH_PROD), 
					_context.applyInt(falsebr, falsedd, DD.ARITH_PROD), 
				DD.ARITH_SUM);
		}
		
		policyCache.put(new Pair<Integer,Map<String,Boolean>>(dd, assign),ret);
		
		return ret;
	}
	
	@Override
	public int getMaximalPath(int dd) {
		
		TreeMap<String, Boolean> actAssigns = new TreeMap<String, Boolean>();
		ADDNode nod = _context.getNode(dd);
		
		while( true ){
			if( nod instanceof ADDDNode ){
				break;
			}else{
				ADDINode n = (ADDINode)nod;

				int varid = n._nTestVarID;
				String var = getActualVarName(varid);
				
//				Syste
				Pair<PVAR_NAME, ArrayList<LCONST>> act = _tmActionVars.get(var);
				Boolean defaultval = (Boolean) _state.getDefaultValue(act._o1);
				
				ADDNode truthnode = _context.getNode(n._nHigh);
				ADDNode falsenode = _context.getNode(n._nLow);
				
				double max = n._dMaxUpper;
				double maxt, maxf;
				
				if( truthnode instanceof ADDDNode ) {
					maxt = ((ADDDNode)truthnode)._dUpper;
				}else {
					maxt = ((ADDINode)truthnode)._dMaxLower;
				}
				
				if( falsenode instanceof ADDDNode ) {
					maxf = ((ADDDNode)falsenode)._dUpper;
				}else {
					maxf = ((ADDINode)falsenode)._dMaxLower;
				}
				
				if( max == maxf ) {
					if( defaultval != false ) {
						actAssigns.put(var, false);
					}
					nod = falsenode;
				}else {
					if( defaultval != true ) {
						actAssigns.put(var, true);
					}
					nod = truthnode;
				}
			}
		}
		
		Map<Integer, Boolean> intAssigns = convertToInts(actAssigns);
		
		return _context.getConstantNode(getJointActionID(intAssigns));
	}
	
	/**
	 * @param pActAssigns
	 * @return
	 */
	


	/* (non-Javadoc)
	 * @see mdp.id.RDDL2DD#thresholdAt(int, double, int[])
	 */
	@Override
	public int thresholdAt(int dd, double val) {
		int ret = _context.AddtoBdd(dd, val);
//		System.out.println(_context.threshHit);
		_context.threshHit = 0;
		_context.threshCache.clear();
		return ret;
	}


	/* (non-Javadoc)
	 * @see mdp.id.RDDL2DD#getActualVarName(int)
	 */
	@Override
	public String getActualVarName(int ddID) {
		return _tmID2VarName.get(_context._hmID2VarName.get(ddID));
	}


	/* (non-Javadoc)
	 * @see mdp.id.RDDL2DD#approximate(int, double)
	 */
	@Override
	public int approximate(int dd, double epsilon) {
		_context.PRUNE_PRECISION = epsilon;
		_context.PRUNE_TYPE = DD.REPLACE_RANGE;
		return _context.pruneNodes(dd);
	}

	/* (non-Javadoc)
	 * @see mdp.id.RDDL2DD#swapForPrimes(int)
	 */
	@Override
	public int swapForPrimes(int dd) {
		int ret = _context.remapGIDsInt(dd, _hmPrimeRemap);
		if( !_context.verifyOrder(ret) ) {
			ret = _context.align(ret);
		}
		return ret;
	}

	/* (non-Javadoc)
	 * @see mdp.id.RDDL2DD#toContextID(java.lang.String)
	 */
	@Override
	public int toContextID(String varname) {
		return (Integer)_context._hmVarName2ID.get((_tmVarName2ID.get(varname)));
	}

	/* (non-Javadoc)
	 * @see mdp.id.RDDL2DD#makePrimeRemap()
	 */
	@Override
	protected void makePrimeRemap() {
		for( String s : _alstatevars ) {
			String ns = s + "'";
			_hmPrimeRemap.put(toContextID(s), 
					toContextID(ns));
		}
	}

	@Override
	protected String toContextString(String varname) {
		return String.valueOf(_context._hmID2VarName.get(_tmVarName2ID.get(varname)));
	}

	/* (non-Javadoc)
	 * @see mdp.id.RDDL2DD#makeNegInfDD(int)
	 */
	@Override
	protected int makeNegInfDD(int dd, double val){
		int ret = makeNegInfDDHelper(dd, val);
//		System.out.println(axHit);
		makeNegInfCache.clear();
		return ret;
	}
	
	private int makeNegInfDDHelper(int dd, double val ) {
		
		if( makeNegInfCache .get(new Pair<Integer, Double>(dd, val) ) != null ) {
//			++argMaxHit;
			return makeNegInfCache.get(new Pair<Integer, Double>(dd, val) );
		}
		
		ADDNode node = _context.getNode(dd);
		if( node instanceof ADDINode ) {
			ADDINode inode = (ADDINode)node;
			int truedd = makeNegInfDD(inode._nHigh, val);
			int falsedd = makeNegInfDD(inode._nLow, val);
			
			int trueret , falseret;
			
			if( truedd == DD_NEG_INF ) {
				trueret = _context.getVarNode(inode._nTestVarID, 0.0d, getNegInf());
			}else {
				trueret = _context.applyInt(
						_context.getVarNode(inode._nTestVarID, 0.0d, 1.0d),
						truedd,
						DD.ARITH_PROD);
			}

			
			if( falsedd == DD_NEG_INF ) {
				falseret = _context.getVarNode(inode._nTestVarID, getNegInf(), 0.0d);
			}else {
				falseret = _context.applyInt(
						_context.getVarNode(inode._nTestVarID, 1.0d, 0.0d),
						falsedd,
						DD.ARITH_PROD);
			}
			
			int ret = _context.applyInt(trueret, falseret, DD.ARITH_SUM);
			makeNegInfCache.put(new Pair<Integer, Double>(dd, val), ret);
			return ret;
		}else {
			ADDDNode dnoe = (ADDDNode)node;
			if( dnoe._dUpper == val ) {
				return DD_NEG_INF;
			}else {
				return DD_ZERO;
			}
		}
	}

//	/* (non-Javadoc)
//	 * @see mdp.id.RDDL2DD#fixInPlace(int)
//	 */
//	@Override
//	public int fix(int dd) {
//		int ret = fixHelper(dd);
//		return ret;
//	}
//
//	/**
//	 * @param pNode
//	 */
//	private int fixHelper(int dd) {
//		
//		ADDNode node = _context.getNode(dd);
//		
//		if( node instanceof ADDDNode ) {
//			double leafval = ((ADDDNode)node)._dUpper;
//			if( leafval == getNegInf() ){
//				return DD_ZERO;
//			}else{
//				return dd;
//			}
//		}
//		
//		ADDINode inode = (ADDINode)node;
//		if( inode._dMinLower != getNegInf() ) {
//			return dd;
//		}
//		
//		int truebr;
//		int truedd = fixHelper(inode._nHigh);
//		truebr = _context.getVarNode(inode._nTestVarID, 0.0d, 1.0d);
//		truebr = _context.applyInt(truebr, truedd, DD.ARITH_PROD);
//
//
//		int falsebr;
//		int falsedd = fixHelper(inode._nLow);
//		falsebr = _context.getVarNode(inode._nTestVarID, 1.0d, 0.0d);
//		falsebr = _context.applyInt(falsebr, falsedd, DD.ARITH_PROD);
//		
//		return _context.applyInt(falsebr, truebr, DD.ARITH_SUM);
//		
//	}

	/* (non-Javadoc)
	 * @see mdp.id.RDDL2DD#getNewContext()
	 */
	@Override
	public Pair<ADD, TreeMap<Integer, String>> getNewContext() {
		return new Pair<ADD, TreeMap<Integer, String>>(new ADD(_alOrder), toContextIDs(_tmVarName2ID));
	}

	@Override
	public String getVarName(int id) {
		return _tmID2VarName.get(id);
	}

	/* (non-Javadoc)
	 * @see mdp.id.RDDL2DD#addPolicyConstraint(int)
	 */
	@Override
	public void addPolicyConstraint(int pPi) {
		_hsPairConstraints.add(new Pair<Integer, Integer>(-1, pPi));
	}

	/* (non-Javadoc)
	 * @see mdp.id.RDDL2DD#removePolicyConstraint(int)
	 */
	@Override
	public void removePolicyConstraint(int pPi) {
		_hsPairConstraints.remove(new Pair<Integer, Integer>(-1, pPi));
	}


}